package Mdown

import (
	"gitee.com/yuyuaa/easy-go-tool/gofunc"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
)

type mdown struct {
	url          string //下载地址
	target       string //下载后的名字
	thread       int    //代表该文件一个分多少分下载
	timeout      time.Duration
	onProgress   func(progress float32, speed float32) //当有进度产生时 传入的是类似于3.14%这样的值,speed 下载速度类似于1.23kb/s这样显示
	wg           sync.WaitGroup
	fileLength   int
	lastFileSize int64 //上一秒的文件大小用来计算速度
}

const CACHEFILE = "./mdownCache"

func New(url string, target string, thread int, timeout time.Duration, onProgress func(progress float32, speed float32)) *mdown {
	return &mdown{
		url:        url,
		target:     target,
		thread:     thread,
		timeout:    timeout,
		onProgress: onProgress,
	}
}

func (self *mdown) Down() (err error) {
	//先判断存放临时文件的文件夹是存在
	filefunc := gofunc.FileFunc{}
	filefunc.ExistDirIfNotCreat(CACHEFILE)
	err = self._getFileLeng()
	if err != nil {
		return err
	}
	hasDownDone := make(chan bool, 1)
	go self.runProgress(hasDownDone)
	return self._down(hasDownDone)
}

//获取文件大小
func (self *mdown) _getFileLeng() (err error) {
	var res *http.Response
	res, err = http.Head(self.url)
	if err != nil {
		return err
	}
	maps := res.Header
	length, err := strconv.Atoi(maps["Content-Length"][0]) // Get the content length from the header request
	if err != nil {
		return err
	}
	self.fileLength = length
	return nil
}

func (self *mdown) _down(hasDownDone chan bool) (err error) {

	defer func() {
		hasDownDone <- true
	}()

	limit := self.thread               // 分成多少个线程下载下载
	len_sub := self.fileLength / limit // 每一份多大
	diff := self.fileLength % limit    // 最后一份多大

	//看一下cache文件中的文件数是否和limit对应不是的话就整个删除
	//fileFunc:=gofunc.FileFunc{}
	//flist:=fileFunc.LsNotDir(CACHEFILE)
	//if len(flist)!=0 && len(flist)!=limit{
	//	os.Remove(CACHEFILE)
	//}

	//如果一个分片下载完成了就将标志位置位1
	var flagComplete = make([]byte, limit)
	for i := 0; i < limit; i++ {
		self.wg.Add(1)
		min := len_sub * i       // Min range
		max := len_sub * (i + 1) // Max range
		if i == limit-1 {
			max += diff
		}
		req, _ := http.NewRequest("GET", self.url, nil)
		var transport http.RoundTripper = &http.Transport{
			DisableKeepAlives: true,
		}
		client := http.Client{
			Transport: transport,
			Timeout:   self.timeout,
		}
		go func(min int, max int, i int) {
			var temp_file *os.File
			for {
				//临时文件先下载到./mdownCache下面
				filename := CACHEFILE + "/" + self.target + "." + strconv.Itoa(i)
				fileinfo, err := os.Stat(filename)
				filesize := 0
				if err != nil {
					var e error
					temp_file, e = os.Create(filename)
					if e != nil {
						log.Println("创建新的临时切片文件失败", e)
						continue
					}
				} else {
					log.Println("找到对应切片文件", i)
					temp_file, err = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, 0777)
					if err != nil {
						log.Println("Open chunk file failed", err)
						os.Remove(filename)
					} else {
						filesize = int(fileinfo.Size())
					}
					if min+filesize == max {
						log.Println("切片文件 ", i, "下载完成")
						temp_file.Close()
						flagComplete[i] = 0x01 //完成下载
						break
					}
					if min+filesize > max {
						temp_file.Close()
						log.Println("start is bigger than finish")
						os.Remove(filename)
						continue
					}
				}
				bytesrange := "bytes=" + strconv.Itoa(min+filesize) + "-" + strconv.Itoa(max-1)
				req.Header.Add("Range", bytesrange)

				resp, e := client.Do(req)
				if e != nil {
					log.Println("[request error],retry:", e)
					temp_file.Close()
					continue
				}
				_, e = io.Copy(temp_file, resp.Body)
				if e != nil {
					log.Println("[copy error],retry", e)
					temp_file.Close()
					resp.Body.Close()
					flagComplete[i] = 0x00 //没有完成下载
					break
					//continue
				}
				temp_file.Close()
				resp.Body.Close()
				flagComplete[i] = 0x01 //完成下载
				break
			}
			self.wg.Done()
		}(min, max, i)
	}
	self.wg.Wait()

	//检查是否所有分片都完成下载
	bytefunc := gofunc.ByteFunc{}
	if bytefunc.IndexOf(flagComplete, 0x00) == -1 { //没有未完成的
		log.Println("完成所有分片下载")
		os.Remove(self.target)
		f, _ := os.OpenFile(self.target, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 777)
		for j := 0; j < limit; j++ {
			chunkf, _ := os.Open(CACHEFILE + "/" + self.target + "." + strconv.Itoa(j))
			_, err = io.Copy(f, chunkf)
			if err != nil {
				log.Println("[merge error]")
			}
			chunkf.Close()
			//os.Remove(CACHEFILE + "/"+self.target + "." + strconv.Itoa(j))
		}
		f.Close()
		//删除临时文件夹
		os.RemoveAll(CACHEFILE)
		return
	} else {
		log.Println("还有分片文件未完成下载")
		return
	}
}

//监测文件大小（通过直接计算文件的大小),并且触发回调
func (self *mdown) runProgress(hasDown chan bool) {
	filefunc := gofunc.FileFunc{}
	size, err := filefunc.DirSize(CACHEFILE)
	if err != nil {
		log.Println(err)
		return
	}
	log.Println(size, "--", self.fileLength, "--", float32(size)/float32(self.fileLength))
	percent := float32(size) / float32(self.fileLength) * 100
	self.lastFileSize = size
	self.onProgress(percent, 0) //执行回调
	ticker := time.NewTicker(1 * time.Second)
	//就是判断
	for {
		select {
		case <-hasDown:
			//删除整个文件夹
			//os.RemoveAll(CACHEFILE)
			return
		case <-ticker.C:
			//通过临时文件的大小
			size, err := filefunc.DirSize(CACHEFILE)
			if err != nil {
				log.Println(err)
				return
			}
			log.Println(size, "--", self.fileLength, "--", float32(size)/float32(self.fileLength))
			speed := float32(size-self.lastFileSize) / float32(1024)
			self.lastFileSize = size
			percent := float32(size) / float32(self.fileLength) * 100
			self.onProgress(percent, speed) //执行回调
		}
	}

}
